package org.as3search.criteria.impl {
	/*
	 * as3search - http://code.google.com/p/as3search
	 * Copyright (c) 2009-2010 by respective authors. All rights reserved.
	 *
	 * This library is free software; you can redistribute it and/or modify it under the
	 * terms of the GNU Lesser General Public License as published by the Free Software
	 * Foundation; either version 2.1 of the License, or (at your option) any later
	 * version.
	 *
	 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
	 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
	 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
	 *
	 * You should have received a copy of the GNU Lesser General Public License along
	 * with this library; if not, write to the Free Software Foundation, Inc.,
	 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
	 */
	
	import org.as3search.Search;
	import org.as3search.config.SearchConfiguration;
	import org.as3search.config.SearchConfigurationAware;
	import org.as3search.criteria.Criteria;
	import org.as3search.criteria.Criterion;
	import org.as3search.criteria.SubCriteria;
	import org.as3search.properties.PropertyResolverAware;
	import org.as3search.providers.SearchProvider;
	
	/**
	 * <code>CriteriaImpl</code> is the default implementation of 
	 * <code>Criteria</code> provided by as3search. For developers
	 * looking to create their own <code>Criteria</code> implementations,
	 * this is a great place to start. 
	 * 
	 * @see org.as3search.criteria.Criteria
	 */
	public class CriteriaImpl implements Criteria {
		
		private var _provider:SearchProvider;
		
		private var _configuration:SearchConfiguration;
		
		private var _criteriaCollection:Array = [];
		
		private var _subCriteriaCollection:Array = [];
		/**
		 * Creates a new <code>CriteriaImpl</code> instance.
		 * @param	provider		The <code>SearchProvider</code> to use as an iterator over the 
		 * 		wrapped collection.
		 * @param	configuration	The <code>SearchConfiguration</code> used to resolve properties, 
		 * 		examine collections, etc. 
		 */
		public function CriteriaImpl(provider:SearchProvider, configuration:SearchConfiguration) {
			if (provider == null || provider.target == null)
				throw new ArgumentError("'provider' and 'provider.target' must neither be null!");
			
			if (configuration == null) 
				configuration = Search.configuration;
			
			_provider = provider;
			_configuration = configuration;
		}
		/**
		 * @inheritDoc
		 */
		public function createCriteria(propertyName:String, criterion:Criterion):Criteria {
			subCriteriaCollection.push(new SubCriteriaImpl(propertyName, criterion,
				configuration, this));
			return this;
		}
		/**
		 * @inheritDoc
		 */
		public function add(value:Criterion):Criteria {
//			check if null
			if (value == null)
				throw new ArgumentError("Criterion passed to add() must not be null.");			
//			this should never happen, but just to be safe...
			if (criteriaCollection == null)
				criteriaCollection = [];
//			if it already is present, throw up			
			if (criteriaCollection.indexOf(value) != -1)
				throw new ArgumentError("You cannot add the same criterion twice!");
//			if it has some dependencies, wire 'em up!
			if (value is SearchConfigurationAware)
				SearchConfigurationAware(value).configuration = this.configuration;
			if (value is PropertyResolverAware) 
				PropertyResolverAware(value).propertyResolver = this.configuration.propertyResolver;
//			add it to the collection
			criteriaCollection.push(value);
//			return self
			return this;
		}
		/**
		 * @inheritDoc
		 */
		public function uniqueResult():Object {
			provider.reset();
			while (provider.hasNext()) {
				var item:Object = provider.next();
				var matches:uint = 0;
				for (var i:uint = 0; i < criteriaCollection.length; i++) {
					var criterion:Criterion = Criterion(criteriaCollection[i]);
					if (criterion.evaluate(item))
						matches++;
					else
						break;
				}
				
				for (i = 0; i < subCriteriaCollection.length; i++) {
					var subCriteria:SubCriteria = SubCriteria(subCriteriaCollection[i]);
					if (subCriteria.evaluate(item))
						matches++;
					else
						break;
				}
				
				if (matches == (criteriaCollection.length + subCriteriaCollection.length))
					return item;
			}
			
			return null;
		}
		/**
		 * @inheritDoc
		 */		
		public function list():Array {
			provider.reset();
			
			var matchList:Array = [];
			while (provider.hasNext()) {
				var item:Object = provider.next();
				var matches:uint = 0;
//				main criteria
				for (var i:uint = 0; i < criteriaCollection.length; i++) {
					var criterion:Criterion = Criterion(criteriaCollection[i]);
					if (criterion.evaluate(item))
						matches++;
					else
						break;
				}
//				subcollection criteria
				for (i = 0; i < subCriteriaCollection.length; i++) {
					var subCriteria:SubCriteria = SubCriteria(subCriteriaCollection[i]);
					if (subCriteria.evaluate(item))
						matches++;
					else
						break;
				}
//				if it meets the criteria, add it to the return list
				if (matches == (criteriaCollection.length + subCriteriaCollection.length))
					matchList.push(item);
			}
			
			return matchList;
		}
		/**
		 * @inheritDoc
		 */
		public function test():Boolean {
			return uniqueResult() != null;
		}
		/**
		 * @inheritDoc
		 */
		public function get provider():SearchProvider { return _provider; }
		/**
		 * @inheritDoc
		 */
		public function get configuration():SearchConfiguration { return _configuration; }
		/**
		 * The <code>Array</code> storing all <code>Criterion</code> instances used to
		 * evaluate objects in this <code>CriteriaImpl</code> instance.
		 * 
		 * @see org.as3search.criteria.Criterion
		 */
		public function get criteriaCollection():Array { return _criteriaCollection; }
		public function set criteriaCollection(value:Array):void {
			if (value == null)
				value = [];
			_criteriaCollection = value;
		}
		/**
		 * The <code>Array</code> storing all <code>SubCriteria</code> instances used to
		 * search through subcollections of each object in the <code>SearchProvider</code>.
		 * 
		 * @see org.as3search.criteria.SubCriteria
		 */
		public function get subCriteriaCollection():Array { return _subCriteriaCollection; }
		public function set subCriteriaCollection(value:Array):void {
			if (value == null)
				value = [];
			_subCriteriaCollection = value;
		}
	}
}